package com.xinxing.learning.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

/**
 * security 自定义配置
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // @Bean
    // public UserDetailsService userDetailsService() {
    //     InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
    //     inMemoryUserDetailsManager.createUser(User.withUsername("aaa").password("{noop}123").roles("admin", "supper").build());
    //     return inMemoryUserDetailsManager;
    // }

    @Bean
    public UserDetailsService customerDetailsService() {
        return new CustomerUserDetailsService();
    }

    /**
     * 自定义AuthenticationManager实现用户认证
     * https://spring.io/guides/topicals/spring-security-architecture/
     * 默认是无法在Spring容器的其他组件中注入该AuthenticationManager对象的
     * 需要通过{@link #authenticationManagerBean()}方法进行暴露到工厂中后，才能进行注入
     *
     * @param authenticationManagerBuilder
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        // 修改的是默认的DaoAuthenticationProvider中的UserDetailsService
        authenticationManagerBuilder.userDetailsService(customerDetailsService());
    }

    /**
     * 作用: 自定义的AuthenticationManager{@link #configure(AuthenticationManagerBuilder authenticationManagerBuilder)}在Spring工厂中进行暴露
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 自定义资源认证规则
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() // 开启http请求认证
                .mvcMatchers("/login.html").permitAll()
                .mvcMatchers("/index").permitAll() // 放行的http请求
                .anyRequest().authenticated() // 任何请求在认证之后才能访问 mvcMatchers必须在anyRequest之前配置
                .and()
                .formLogin() // 认证方式 form表单认证
                .loginPage("/login.html") // 默认登录页面的请求 注意：一旦指定了登录页面 必须指定处理登录的url
                .loginProcessingUrl("/login") // 指定处理登录请求的url
                .usernameParameter("uname") // 指定登录请求参数 用户名的key
                .passwordParameter("pwd")// 指定登录请求 登录密码的key
                // ------------传统web开发 starter------------
                // .successForwardUrl("/index") // 认证成功后 forward转发跳转的请求路径 始终在认证成功后跳转到指定请求 因为是forward转发模式，所以浏览器的请求路径不会改变，但是返回的结果是该路径的结果
                // .defaultSuccessUrl("/index") // 同successForwardUrl()方法类似 认证成功后 redirect重定向到指定路径 因为是redirect重定向的模式，所以浏览器的地址会改变 如果在认证之前访问的是受限资源导致的认证请求，认证成功后会跳转到认证之前访问的受限页面 如果是直接请求的认证请求 认证成功后会访问该路径资源
                // .defaultSuccessUrl("/index", true) // 同defaultSuccessUrl 不同的是不管是访问受限资源进行的认证还是直接访问认证的请求 认证成功后都直接访问该路径

                /**
                 * 源码位置 认证失败后对异常的处理 {@link SimpleUrlAuthenticationFailureHandler#onAuthenticationFailure}
                 */
                // .failureForwardUrl("/login.html") // 认证失败后 通过forward转发进行跳转到指定资源 错误提示信息保存在request作用于中 key=SPRING_SECURITY_LAST_EXCEPTION
                // .failureUrl("/login.html") // 认证失败后 通过redirect重定向进行跳转到指定资源 错误提示信息保存在session作用于中 key=SPRING_SECURITY_LAST_EXCEPTION
                // ------------传统web开发 end------------

                // ------------前后分离开发 starter------------
                .successHandler(new CustomerAuthenticationSuccessHandler()) // 前后端分离开发 认证成功后返回JSON格式数据
                .failureHandler(new CustomerAuthenticationFailureHandler()) // 认证失败后 自定义处理方式 适合前后端分离
                // ------------前后分离开发 end------------
                .and()
                .logout() // 退出登录
                .logoutUrl("/logout") // 指定注销登录请求的url 默认就是地址就是/logout 默认是 GET请求方式
                // 指定推出的请求URL 和 请求方式 只要匹配任意一个就可以成功退出登录 配置这个后 上面的logoutUrl方法就会失效
                .logoutRequestMatcher(new OrRequestMatcher(
                        new AntPathRequestMatcher("/aa", "GET"),
                        new AntPathRequestMatcher("/bb", "POST")
                ))
                .invalidateHttpSession(true) //默认为true 退出登录后 让会话失效
                .clearAuthentication(true) //默认为true 退出登录后 清除认证信息
                // .logoutSuccessUrl("/login.html") // 注销登录后 跳转的页面 传统web开发
                .logoutSuccessHandler(new CustomerLogoutSuccessHandler()) // 前后端分离 注销登录成功后的处理
                .and()
                .csrf().disable() // 禁止csrf跨站保护
        ;
    }
}
